package square;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Random;

import be.kuleuven.cs.som.annotate.Basic;
import be.kuleuven.cs.som.annotate.Immutable;
import be.kuleuven.cs.som.annotate.Raw;
import border.Border;

/**
 * A class for creating teleportation squares as special kinds of squares involving
 * as additional properties a teleportation facility, a minimal and a 
 * maximal temperature value.
 * 
 * @invar	Each teleportation square must have a valid humidity
 * 			| isValidHumidity(getHumidity())
 * @invar	Each square must have a valid temperature.
 * 			| isValidTemperature(getTemperature())
 * 
 * @version	3.0
 * @author	Inge Gielis <inge.gielis@student.kuleuven.be>
 * @author 	Annelies Ketelslegers <annelies.ketelslegers@student.kuleuven.be>
 */
public abstract class TeleportationSquare extends Square {
	
	/**
	 * Initialize this new teleporation square with given temperature and given humidity
	 * and attach this teleportation square to the given border in the given direction.
	 * 
	 * @param 	temperature
	 * 			The temperature for this new teleportation square.
	 * @param 	humidity
	 * 		  	The humidity for this new square.
	 * @param	border
	 * 			The border that must be attached to this square.
	 * @param	direction
	 * 			The direction in which the given border must be attached.
	 * @post  	The new temperature for this new square is equal to
	 * 			the given temperature.
	 * 			| new.getTemperature() == temperature
	 * @post  	The new humidity of this new square is equal to the given humidity.
	 * 			| new.getHumidity() == humidity
	 * @post	The given border was attached in the given direction.
	 * 			| new.getBorder(direction) == border
	 * @throws 	IllegalArgumentException 
	 * 			The given temperature is not a valid value
	 * 			| ! isValidTemperature(temperature)
	 */
	public TeleportationSquare(Temperature temperature, BigDecimal humidity, Border border, Direction direction) {
		setTemperature(temperature);
		setHumidity(humidity);
		border.constructBorder(this, direction);
	}
	
	/**
	 * Initialize this new teleporation square with given temperature and given humidity.
	 *
	 * @param 	temperature
	 * 			The temperature for this new teleportation square.
	 * @param 	humidity
	 * 		  	The humidity for this new square.
	 * @post  	The new temperature for this new square is equal to
	 * 			the given temperature.
	 * 			| new.getTemperature() == temperature
	 * @post  	The new humidity of this new square is equal to the given humidity.
	 * 			| new.getHumidity() == humidity
	 * @throws 	IllegalArgumentException 
	 * 			The given temperature is not a valid value
	 * 			| ! isValidTemperature(temperature)
	 */
	protected TeleportationSquare(Temperature temperature, BigDecimal humidity){
		setTemperature(temperature);
		setHumidity(humidity);
	}
	
	/**
	 * Return the lower limit of the temperature that applies to all squares.
	 * 		The lower limit of the temperature expresses the lowest possible value
	 * 		for the temperature of a square.
	 */
	@Basic
	@Immutable
	public abstract Temperature getMinTemperature();

	/**
	 * Return the upper limit for the temperature that applies to all squares.
	 * 		The upper limit of the temperature expresses the highest possible value
	 * 		for the temperature of a square.
	 */
	@Basic
	@Immutable
	public abstract Temperature getMaxTemperature();
	
	/**
	 * Set the temperature for this teleportation square to the given temperature.
	 * 
	 * @param 	temperature
	 * 			The new temperature for this square.
	 * @post 	The new temperature is equal to the given temperature.
	 * 			| new.getTemperature() == temperature
	 * @throws 	IllegalArgumentException
	 * 			The value is not located between the given limits.
	 * 			|!isValidTemperature(temperature) 
	 */
	@Raw
	public void setTemperature(Temperature temperature) throws IllegalArgumentException {
		if(!isValidTemperature(temperature))
			throw new IllegalArgumentException("The value of the temperature has to be located between " 
					+ getMinTemperature() + " and " + getMaxTemperature());
		this.temperature = temperature;
	}

	/**
	 * Check whether the given temperature is a valid temperature.
	 * 
	 * @param	temperature
	 * 			The temperature to check.
	 * @return 	True if and only if the given temperature is located between the given limits.
	 * 			|result ==
	 * 			|	(temperature >= getMinTemperature())
	 * 			|	&&(temperature <= getMaxTemperature())
	 * @throws	ClassCastException
	 * 			the given temperature was not effective
	 * 			|temperature == null
	 */
	@Raw
	public boolean isValidTemperature(Temperature temperature)
			throws ClassCastException {
		return (getMinTemperature().compareTo(temperature)) <= 0 
					&&(getMaxTemperature().compareTo(temperature)>= 0);
	}

	/**
	 * Return the humidity for this teleportation square.
	 */
	@Override
	@Basic
	@Raw
	public BigDecimal getHumidity(){
		return this.humidity;
	}

	/**
	 * Set the humidity for this square to the given value.
	 * 
	 * @pre		The humidity must be a valid value.
	 * 			| isValidHumidity(humidity) = true
	 * @param 	humidity
	 * 			The humidity to set for this square.
	 * @post	The new humidity is equal to the given humidity.
	 * 			| new.getHumidity() == humidity;
	 */
	@Raw
	public void setHumidity(BigDecimal humidity) {
		assert(isValidHumidity(humidity));
		this.humidity = humidity;
	}

	/**
	 * Check whether the given value is a valid humidity.
	 * 
	 * @param 	humidity
	 * 			The humidity to check.
	 * @return	True if and only if the humidity is located between 0 and 100 
	 * 			and if it has only 2 decimal digits.
	 * 			| result == (new BigDecimal(100).compareTo(humidity)>= 0) 
	 * 			|		&& (new BigDecimal(0).compareTo(humidity)<= 0)&&(humidity.scale() <= 2)
	 */
	@Raw
	public static final boolean isValidHumidity(BigDecimal humidity) {
		return (new BigDecimal(100).compareTo(humidity)>= 0) 
					&& (new BigDecimal(0).compareTo(humidity)<= 0)
					&&(humidity.scale() <= 2);
	}

	/**
	 * Variable registering the humidity for this square.
	 */
	protected BigDecimal humidity;

	/**
	 * Remove the given border.
	 * 
	 * @post 	The border is removed from the square 
	 * 			if the given border is effective 
	 * 			and if this square is attached to the given border 
	 * 			and if the given border does not reference this teleportation square as one of its squares
	 * 			and if this teleportation square is terminated or if the number of borders of this teleportation square exceeds one.
	 * 			|if((hasBorder(border))&&
	 *			|		(! border.hasAsSquare(this)) &&
	 *			|		(this.isTerminated() || getNbBorders() >1))		
	 * 			|	then (new.hasBorder(border) == false 
	 * 			|			&& new.getNbBorders() == this.getNbBorders() - 1) 
	 */
	@Override
	public void removeBorder(Border border) {
		if((hasBorder(border))&&
				(! border.hasAsSquare(this))
					&& (this.isTerminated() || getNbBorders() >1))
			borders.remove(getDirectionOfBorder(border));
	}

	/**
	 * Return all possible destinations to which this square can teleport.
	 */
	@Basic
	public ArrayList<TeleportationSquare> getTeleportationDestinations(){
		return teleportationDestinations;
	}

	/**
	 * Check whether it is possible to teleport from this square to the given square.
	 * 
	 * @param 	destinationSquare
	 * 			The square to be checked.
	 */
	@Basic
	public boolean canTeleportTo(TeleportationSquare destinationSquare){
		return teleportationDestinations.contains(destinationSquare);
	}

	/**
	 * Add the given square to the possible destinations for teleportation.
	 * 
	 * @pre		The given destination must be an effective square and may
	 * 			not already be terminated
	 * 			| destination != null && ! destination.isTerminated()
	 * @pre		It is not yet possible to teleport to the given square.
	 * 			| ! this.canTeleportTo(destination)
	 * @param 	destination
	 * 			The square to be added.
	 * @post	It will be possible to teleport to the given square.
	 * 			| new.canTeleportTo(destination) == true
	 */
	public void addTeleportationDestination(TeleportationSquare destination){
		assert(destination != null && ! destination.isTerminated());
		assert(! canTeleportTo(destination));
		teleportationDestinations.add(destination);
	}
	
	/**
	 * Return the square to which will be teleported.
	 * This square is randomly picked from the list of teleportation destinations.
	 * 
	 * @return	A square to which it is possible to teleport to from this square.
	 * 			| canTeleportTo(result) == true
	 */
	public TeleportationSquare teleport(){
		Random random = new Random();
		return teleportationDestinations.get(random.nextInt(teleportationDestinations.size()));
	}
	
	/**
	 * Variable referencing a list of teleportation squares to which it is possible to teleport to.
	 */
	private ArrayList<TeleportationSquare> teleportationDestinations = new ArrayList<TeleportationSquare>();
}